package main

import (
	"fmt"
	"reflect"
)

// 运行时反射是程序在运行期间检查其自身结构的一种方式。
// 反射带来的灵活性是一把双刃剑，反射作为一种元编程方式可以减少重复代码，但是过量的使用反射会使我们的程序逻辑变得难以理解并且运行缓慢
// 我们在这一节中会介绍 Go 语言反射的三大法则3，其中包括：

// 从 interface{} 变量可以反射出反射对象；
// 从反射对象可以获取 interface{} 变量；
// 要修改反射对象，其值必须可设置；

func main() {
	// 从接口值到反射对象：
	// 从基本类型到接口类型的类型转换；
	// 从接口类型到反射对象的转换；
	author := "draven"
	fmt.Println("TypeOf author:", reflect.TypeOf(author))
	fmt.Println("ValueOf author:", reflect.ValueOf(author))


	// 从反射对象到接口值：
	// 反射对象转换成接口类型；
	// 通过显式类型转换变成原始类型；
	v := reflect.ValueOf(1)
	i := v.Interface().(int)
	fmt.Println(i)


	// panic: reflect: reflect.flag.mustBeAssignable using unaddressable value
	// Go 语言的函数调用都是传值的，所以我们得到的反射对象跟最开始的变量没有任何关系，所以直接对它修改会导致崩溃。
/*	j := 1
	v2 := reflect.ValueOf(j)
	v2.SetInt(10)
	fmt.Println(j)*/


	k := 1
	v3 := reflect.ValueOf(&k)  // 获取变量指针
	v3.Elem().SetInt(10)  // 获取指针指向的变量
	fmt.Println(k)  // 更新变量的值

	// 由于 Go 语言的函数调用都是值传递的，所以我们只能先获取指针对应的 reflect.Value，
	// 再通过 reflect.Value.Elem 方法迂回的方式得到可以被设置的变量，我们通过如下所示的代码理解这个过程
	m := 1
	v4 := &m
	*v4 = 20
	fmt.Println(m)


	// 判断一个类型是否实现了某个接口
	typeOfError := reflect.TypeOf((*error)(nil)).Elem()  // 获得接口的类型
	customErrorPtr := reflect.TypeOf(&CustomError{})
	customError := reflect.TypeOf(CustomError{})

	fmt.Println(customErrorPtr.Implements(typeOfError)) // #=> true
	fmt.Println(customError.Implements(typeOfError)) // #=> false

	AddAction()


}


type CustomError struct{}

func (*CustomError) Error() string {
	return ""
}


func Add(a, b int) int { return a + b }

func AddAction(){
	v := reflect.ValueOf(Add)
	if v.Kind() != reflect.Func {
		return
	}
	t := v.Type()
	argv := make([]reflect.Value, t.NumIn())
	for i := range argv {
		if t.In(i).Kind() != reflect.Int {
			return
		}
		argv[i] = reflect.ValueOf(i+1)
	}
	result := v.Call(argv)
	if len(result) != 1 || result[0].Kind() != reflect.Int {
		return
	}
	fmt.Println(result[0].Int()) // #=> 1
}

